package com.castrianni.vcs.cvs;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedHashMap;

import javax.swing.DefaultListModel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.jetbrains.annotations.Nullable;
import org.jgraph.event.GraphSelectionEvent;
import org.jgraph.graph.DefaultGraphCell;
import com.castrianni.vcs.VcsBranch;
import com.castrianni.vcs.VcsRevisionGraph;
import com.castrianni.vcs.VcsRevisionGraphCellViewFactory;
import com.castrianni.vcs.VcsRevisionGraphProjectComponent;
import com.castrianni.vcs.VcsRevisionGraphRevision;
import com.intellij.cvsSupport2.cvsExecution.CvsOperationExecutor;
import com.intellij.cvsSupport2.cvsExecution.CvsOperationExecutorCallback;
import com.intellij.cvsSupport2.cvshandlers.CommandCvsHandler;
import com.intellij.cvsSupport2.cvshandlers.FileSetToBeUpdated;
import com.intellij.cvsSupport2.history.CvsFileRevision;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
import com.intellij.openapi.vcs.history.VcsHistorySession;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import info.clearthought.layout.TableLayout;

public class CVSRevisionGraph extends VcsRevisionGraph {
  protected DefaultListModel _tagsLM = null;
  protected AddTagAction _ata = null;
  protected RemoveTagAction _rta = null;
  protected MoveMergeTagsAction _mmta = null;
  protected ToggleShowTagsAction _tsta = null;
  protected TagFilterAction _tfa = null;

  public CVSRevisionGraph(Project project,FilePath filePath,AbstractVcs vcs,VcsHistoryProvider historyProvider)
  {
    super(project,filePath,vcs,historyProvider);
    init();
  }

  protected VcsRevisionGraphCellViewFactory createCellViewFactory()
  {
    CVSRevisionGraphCellViewFactory retVal = new CVSRevisionGraphCellViewFactory(_project);
    VcsRevisionGraphProjectComponent rgpc = _project.getComponent(VcsRevisionGraphProjectComponent.class);
    retVal.setShowTags(rgpc.is_showTags());
    retVal.setShowTagFilter(rgpc.is_showTagFilter());
    retVal.setTagFilter(rgpc.get_tagFilter());
    return(retVal);
  }

  protected DefaultActionGroup createToolBarActions()
  {
    DefaultActionGroup retVal = super.createToolBarActions();
    _ata = new AddTagAction();
    _rta = new RemoveTagAction();
    _mmta = new MoveMergeTagsAction();
    _tsta = new ToggleShowTagsAction();
    _tfa = new TagFilterAction();
    retVal.add(_ata);
    retVal.add(_rta);
    retVal.add(_mmta);
    retVal.addSeparator();
    retVal.add(_tsta);
    retVal.add(_tfa);
    return(retVal);
  }

  protected DefaultActionGroup createFileRevisionActions()
  {
    DefaultActionGroup retVal = super.createFileRevisionActions();
    retVal.addSeparator();
    retVal.add(_ata);
    retVal.add(_rta);
    retVal.add(_mmta);
    return(retVal);
  }

  protected DefaultActionGroup createViewActions()
  {
    DefaultActionGroup retVal = super.createViewActions();
    retVal.addSeparator();
    retVal.add(_tsta);
    retVal.add(_tfa);
    return(retVal);
  }

  @Nullable
  protected JComponent createSouthPanel()
  {
    JPanel retVal = (JPanel)super.createSouthPanel();
    //noinspection ConstantConditions
    TableLayout tl = (TableLayout)retVal.getLayout();
    tl.insertColumn(3,TableLayout.PREFERRED);
    retVal.add(new JLabel("Tags:"),"3,0,f,t");
    tl.insertColumn(4,TableLayout.FILL);
    _tagsLM = new DefaultListModel();
    JList tagsL = new JList(_tagsLM);
    tagsL.setVisibleRowCount(4);
    JScrollPane sp = new JScrollPane(tagsL);
    retVal.add(sp,"4,0");
    return(retVal);
  }

  protected String getAllRevisions()
  {
    //Fill list of revisions from historyProvider
    VcsHistorySession historySession;
    try {
      historySession = _historyProvider.createSessionFor(_filePath);
    } catch(Throwable t) {
      throw new RuntimeException(t.getMessage());
    }
    List<VcsFileRevision> revisions = historySession.getRevisionList();
    _revisions = new ArrayList<VcsRevisionGraphRevision>();
    for(VcsFileRevision rev : revisions) {
      String branchRevision = getParentRevision(rev.getRevisionNumber().asString());
      VcsRevisionGraphRevision rgRev = new VcsRevisionGraphRevision(rev,branchRevision);
      _revisions.add(rgRev);
    }
    //Convert list of revisions into data structures for better searching
    HashMap<String,ArrayList<VcsRevisionGraphRevision>> revMap = new HashMap<String,ArrayList<VcsRevisionGraphRevision>>();
    ArrayList<String> branchRevisionList = new ArrayList<String>();
    HashMap<String,String> branchNameMap = new HashMap<String,String>();
    branchNameMap.put("1","HEAD");
    for(VcsRevisionGraphRevision rev : _revisions) {
      CvsFileRevision cvsRev = (CvsFileRevision)rev.getFileRevision();
      String branchRevision = getParentRevision(cvsRev.getRevisionNumber().asString());
      if(!branchRevisionList.contains(branchRevision))
        branchRevisionList.add(branchRevision);
      Collection<String> branches = cvsRev.getBranches();
      for(String branch : branches) {
        String branchParts[] = branch.split(" *[()]");
        branchNameMap.put(branchParts[1],branchParts[0]);
        if(!branchRevisionList.contains(branchParts[1]))
          branchRevisionList.add(branchParts[1]);
      }
      ArrayList<VcsRevisionGraphRevision> revsOnBranch = revMap.get(branchRevision);
      if(revsOnBranch == null) {
        revsOnBranch = new ArrayList<VcsRevisionGraphRevision>();
        revMap.put(branchRevision,revsOnBranch);
      }
      revsOnBranch.add(rev);
    }
    //Sort everything
    Collections.sort(branchRevisionList,_revCompare);
    for(ArrayList<VcsRevisionGraphRevision> revsOnBranch : revMap.values())
      Collections.sort(revsOnBranch,_revCompare);
    _revMap = new LinkedHashMap<VcsBranch,ArrayList<VcsRevisionGraphRevision>>();
    _branchMap = new LinkedHashMap<String,VcsBranch>();
    //Fill up return value in correct order
    for(String branchRevision : branchRevisionList) {
      String branchName = branchNameMap.get(branchRevision);
      if(branchName == null)
        branchName = "<unnamed>";
      VcsBranch branch = new VcsBranch(branchRevision,branchName);
      String branchPointRevision = getParentRevision(branchRevision);
      branch.setParentBranchPointRevision(branchPointRevision);
      String parentBranchRevision = getParentRevision(branchPointRevision);
      branch.setParentBranchId(parentBranchRevision);
      _branchMap.put(branchRevision,branch);
      ArrayList<VcsRevisionGraphRevision> revsOnBranch = revMap.remove(branchRevision);
      if(revsOnBranch == null)
        revsOnBranch = new ArrayList<VcsRevisionGraphRevision>();
      _revMap.put(branch,revsOnBranch);
    }
    return(historySession.getCurrentRevisionNumber().asString());
  }

  protected String getParentRevision(String revision)
  {
    if(revision == null)
      return(null);
    int index = revision.lastIndexOf('.');
    if(index == -1)
      return(null);
    //Strip off last dot and number
    return(revision.substring(0,index));
  }

  public void addTag(String tagName)
  {
    Object selCells[] = _g.getSelectionCells();
    if((selCells == null) || (selCells.length != 1))
      return;
    boolean doRefresh = true;
    if(tagName == null) {
      tagName = Messages.showInputDialog(_project,"Enter tag name to add:","Add Tag",Messages.getQuestionIcon());
      if(tagName == null)
        return;
    } else
      doRefresh = false;
    DefaultGraphCell selCell = (DefaultGraphCell)selCells[0];
    VcsRevisionGraphRevision selRev = (VcsRevisionGraphRevision)selCell.getUserObject();
    FilePath filePaths[] = new FilePath[1];
    filePaths[0] = _filePath;
    CvsOperationExecutor executor = new CvsOperationExecutor(_project);
    FileSetToBeUpdated fileSet = FileSetToBeUpdated.selectedFiles(filePaths);
    ExtendedTagOperation tagOp = new ExtendedTagOperation(filePaths,tagName,false,true,selRev.getRevisionNumber().asString());
    CommandCvsHandler tagHandler = new CommandCvsHandler("tag",tagOp,fileSet,false);
    executor.performActionSync(tagHandler,CvsOperationExecutorCallback.EMPTY);
    if(doRefresh)
      refresh(true);
  }

  public void removeTag()
  {
    Object selCells[] = _g.getSelectionCells();
    if((selCells == null) || (selCells.length != 1))
      return;
    DefaultGraphCell selCell = (DefaultGraphCell)selCells[0];
    VcsRevisionGraphRevision selRev = (VcsRevisionGraphRevision)selCell.getUserObject();
    CvsFileRevision cSelRev = (CvsFileRevision)selRev.getFileRevision();
    //noinspection unchecked
    Collection<String> selRevLabels = cSelRev.getTags();
    if(selRevLabels == null)
      selRevLabels = new ArrayList<String>();
    String selRevLabelChoices[] = selRevLabels.toArray(new String[0]);
    Arrays.sort(selRevLabelChoices);
    if(selRevLabelChoices.length == 0) {
      Messages.showMessageDialog(_project,"There are no tags on this revision to remove","Invalid Revision",Messages.getErrorIcon());
      return;
    }
    int tagIndex = Messages.showChooseDialog(_project,"Select tag name to remove:","Remove Tag",Messages.getQuestionIcon(),selRevLabelChoices,null);
    if(tagIndex == -1)
      return;
    FilePath filePaths[] = new FilePath[1];
    filePaths[0] = _filePath;
    CvsOperationExecutor executor = new CvsOperationExecutor(_project);
    FileSetToBeUpdated fileSet = FileSetToBeUpdated.selectedFiles(filePaths);
    ExtendedTagOperation tagOp = new ExtendedTagOperation(filePaths,selRevLabelChoices[tagIndex],true,true,selRev.getRevisionNumber().asString());
    CommandCvsHandler tagHandler = new CommandCvsHandler("tag",tagOp,fileSet,false);
    executor.performActionSync(tagHandler,CvsOperationExecutorCallback.EMPTY);
    refresh(true);
  }

  public void moveMergeTags()
  {
    CVSMoveMergeTagsOptions moveTagsOptions = new CVSMoveMergeTagsOptions(_project,_revMap.keySet());
    moveTagsOptions.pack();
    moveTagsOptions.show();
    CVSMoveMergeTagsOptions.MoveMergeTagsOptions options = moveTagsOptions.getReturnValue();
    if(options == null)
      return;
    VcsRevisionGraphProjectComponent rgpc = _project.getComponent(VcsRevisionGraphProjectComponent.class);
    if(options._moveBefore) {
      String beforeTagName = rgpc.getBeforeTagName(options._sourceBranchName,options._destBranchName);
      addTag(beforeTagName);
    }
    if((options._moveAfter) && rgpc.is_useTwoTagConvention()) {
      String afterTagName = rgpc.getAfterTagName(options._sourceBranchName,options._destBranchName);
      addTag(afterTagName);
    }
    refresh(true);
  }

  public void toggleShowTags()
  {
    CVSRevisionGraphCellViewFactory cvf = (CVSRevisionGraphCellViewFactory)_cellViewFactory;
    cvf.setShowTags(!cvf.isShowTags());
    refresh(false);
  }

  public void tagFilter()
  {
    CVSTagFilterOptions dialog = new CVSTagFilterOptions(_project);
    CVSRevisionGraphCellViewFactory cvf = (CVSRevisionGraphCellViewFactory)_cellViewFactory;
    dialog.show(cvf.isShowTagFilter(),cvf.getTagFilter());
    CVSTagFilterOptions.TagFilteringOptions options = dialog.getReturnValue();
    if(options == null)
      return;
    cvf.setShowTagFilter(options._showTagFilter);
    cvf.setTagFilter(options._tagFilter);
    refresh(false);
  }

  public void valueChanged(GraphSelectionEvent gse)
  {
    super.valueChanged(gse);
    _tagsLM.removeAllElements();
    if(_selRevCell != null) {
      CvsFileRevision cvsRev = (CvsFileRevision)_selRevCell.getFileRevision();
      //noinspection unchecked
      Collection<String> revLabelsC = cvsRev.getTags();
      if(revLabelsC != null) {
        ArrayList<String> revLabels = new ArrayList<String>(revLabelsC);
        Collections.sort(revLabels);
        for(String revLabel : revLabels)
          _tagsLM.addElement(revLabel);
      }
    }
  }

  protected class AddTagAction extends AnAction {
    public AddTagAction()
    {
      super("Add Tag to Selected Revision","Add Tag to Selected Revision",IconLoader.getIcon("/com/castrianni/vcs/images/addTag.png"));
    }

    public void actionPerformed(AnActionEvent ae)
    {
      addTag(null);
    }

    public void update(AnActionEvent ae)
    {
      oneFileRevisionCellSelectedRequired(ae.getPresentation());
    }
  }

  protected class RemoveTagAction extends AnAction {
    public RemoveTagAction()
    {
      super("Remove Tag from Selected Revision","Remove Tag from Selected Revision",IconLoader.getIcon("/com/castrianni/vcs/images/removeTag.png"));
    }

    public void actionPerformed(AnActionEvent ae)
    {
      removeTag();
    }

    public void update(AnActionEvent ae)
    {
      oneFileRevisionCellSelectedRequired(ae.getPresentation());
    }
  }

  protected class MoveMergeTagsAction extends AnAction {
    public MoveMergeTagsAction()
    {
      super("Move Merge Tags to Selected Revision","Move Merge Tags to Selected Revision",IconLoader.getIcon("/com/castrianni/vcs/images/moveMergeTags.png"));
    }

    public void actionPerformed(AnActionEvent ae)
    {
      moveMergeTags();
    }

    public void update(AnActionEvent ae)
    {
      oneFileRevisionCellSelectedRequired(ae.getPresentation());
    }
  }

  protected class ToggleShowTagsAction extends AnAction {
    public void actionPerformed(AnActionEvent ae)
    {
      toggleShowTags();
    }

    public void update(AnActionEvent ae)
    {
      Presentation p = ae.getPresentation();
      CVSRevisionGraphCellViewFactory cvf = (CVSRevisionGraphCellViewFactory)_cellViewFactory;
      if(cvf.isShowTags()) {
        p.setText("Hide Tags within Cells");
        p.setDescription("Hide Tags within Cells");
        p.setIcon(IconLoader.getIcon("/com/castrianni/vcs/images/hideTags.png"));
      } else {
        p.setText("Show Tags within Cells");
        p.setDescription("Show Tags within Cells");
        p.setIcon(IconLoader.getIcon("/com/castrianni/vcs/images/showTags.png"));
      }
    }
  }

  protected class TagFilterAction extends AnAction {
    public TagFilterAction()
    {
      super("Set Tag Filter","Set Tag Filter",IconLoader.getIcon("/com/castrianni/vcs/images/tagFilter.png"));
    }

    public void actionPerformed(AnActionEvent ae)
    {
      tagFilter();
    }

    public void update(AnActionEvent ae)
    {
      Presentation p = ae.getPresentation();
      CVSRevisionGraphCellViewFactory cvf = (CVSRevisionGraphCellViewFactory)_cellViewFactory;
      p.setEnabled(cvf.isShowTags());
    }
  }
}
